﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Shell;
using System.Windows.Documents;
using Jive.Resources;
using System.Windows.Shapes;
using System.Text.RegularExpressions;
using System.Threading;
using System.Diagnostics;
using System.Windows.Threading;
using System.ComponentModel;
using System.Windows.Controls.Primitives;
using System.Collections.ObjectModel;
using System.Windows.Media.Imaging;
using Jive.Models;
using Jive.Helpers;
using JmShared;

namespace Jive.CustomControls
{
    public partial class ChatControl : UserControl
    {
        #region static variables

        //private static readonly UserInfo GreenText = new UserInfo { Color = Colors.Green.ToString(), UserName = "GreenText", FontStyle = FontStyles.Normal.ToString(), FontName = "Segoe UI" };
        //private static readonly UserInfo RedText = new UserInfo { Color = Colors.Brown.ToString(), UserName = "RedText", FontStyle = FontStyles.Normal.ToString(), FontName = "Segoe UI" };
        //private static readonly UserInfo GrayText = new UserInfo { Color = Colors.LightGray.ToString(), UserName = "GrayText", FontStyle = FontStyles.Normal.ToString(), FontName = "Segoe UI" };
        private static Regex HyperLinkRegex = new Regex(@"(((file|gopher|news|nntp|telnet|http|ftp|https|ftps|sftp)://)|(www\.))+(([a-zA-ZÆæÅåØø0-9\._-]+\.[a-zA-Z]{2,6})|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}))(/[\w!\&amp;%_\./-~-\(\),\'\[\]+\=#]*)?");
        #endregion
        private Dictionary<MessageModel, ContentPresenter> items;
        private ViewportControl _viewPortControl;
        private DateTime LastScrollAdjustment;
        private object LockObject = new object();
        private Thread _blinkScrollbarThread;
        private bool _isScrollbarBlinking;

        public event EventHandler<SendMessageEventArgs> OnSendMessage;
        public event EventHandler<System.Windows.Input.GestureEventArgs> OnChatWindowTapped;



        public int InputTextSelectionLength
        {
            get { return (int)GetValue(InputTextSelectionLengthProperty); }
            set { SetValue(InputTextSelectionLengthProperty, value); }
        }

        // Using a DependencyProperty as the backing store for InputTextSelectionLength.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty InputTextSelectionLengthProperty =
            DependencyProperty.Register("InputTextSelectionLength", typeof(int), typeof(ChatControl), new PropertyMetadata(0));


        public int InputTextSelectionStart
        {
            get { return (int)GetValue(InputTextSelectionStartProperty); }
            set { SetValue(InputTextSelectionStartProperty, value); }
        }

        // Using a DependencyProperty as the backing store for InputTextSelectionStart.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty InputTextSelectionStartProperty =
            DependencyProperty.Register("InputTextSelectionStart", typeof(int), typeof(ChatControl), new PropertyMetadata(0));

        public string InputText
        {
            get { return (string)GetValue(InputTextProperty); }
            set { SetValue(InputTextProperty, value); }
        }

        // Using a DependencyProperty as the backing store for InputText.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty InputTextProperty =
            DependencyProperty.Register("InputText", typeof(string), typeof(ChatControl), new PropertyMetadata(string.Empty));

        public bool IsInputTextBlockFocused
        {
            get { return (bool)GetValue(IsInputTextBlockFocusedProperty); }
            set { SetValue(IsInputTextBlockFocusedProperty, value); }
        }
        // Using a DependencyProperty as the backing store for IsInputTextBlockFocused.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty IsInputTextBlockFocusedProperty =
            DependencyProperty.Register("IsInputTextBlockFocused", typeof(bool), typeof(ChatControl), new PropertyMetadata(false, new PropertyChangedCallback(IsInputTextBlockFocusedProperty_Callback)));

        public ObservableCollection<MessageModel> Messages
        {
            get { return (ObservableCollection<MessageModel>)GetValue(MessagesProperty); }
            set { SetValue(MessagesProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Messages.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty MessagesProperty =
            DependencyProperty.Register("Messages", typeof(ObservableCollection<MessageModel>), typeof(ChatControl), new PropertyMetadata(null, new PropertyChangedCallback(Messages_Callback)));



        public double InputTextControlOpacity
        {
            get { return (double)GetValue(InputTextControlOpacityProperty); }
            set { SetValue(InputTextControlOpacityProperty, value); }
        }

        // Using a DependencyProperty as the backing store for InputTextControlOpacity.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty InputTextControlOpacityProperty =
            DependencyProperty.Register("InputTextControlOpacity", typeof(double), typeof(ChatControl), new PropertyMetadata(1.0));


        public ChatControl()
        {
            InitializeComponent();
            this.InputTextBox.DataContext = this;
            this.ChatBalloonList.DataContext = this;


        }
        private static void Messages_Callback(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (DesignerProperties.IsInDesignTool)
                return;
            ChatControl cc = d as ChatControl;
            ObservableCollection<MessageModel> messages = e.NewValue as ObservableCollection<MessageModel>;
            if (messages != null)
                messages.CollectionChanged += cc.Messages_CollectionChanged;
            if (messages.Count > 1)
            {
                Thread t = new Thread(() =>
                {
                    Thread.Sleep(500);
                    cc.Dispatcher.BeginInvoke(() =>
                    {
                        cc.SetScrollBar(true);
                    });
                });
                t.Start();
            }
        }
        private static void IsInputTextBlockFocusedProperty_Callback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
        {
            ChatControl control = dependencyObject as ChatControl;
            if ((bool)dependencyPropertyChangedEventArgs.NewValue == true)
                control.InputTextBox.Focus();
            else
                control.Focus();
        }
        void Messages_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
            {
                bool forceScroll = false;
                foreach (MessageModel m in e.NewItems)
                {
                    if (m.IsLastInBatch)
                    {
                        forceScroll = true;
                        break;
                    }
                }
                if (forceScroll)
                {
                    Thread.Sleep(500);
                    Debug.WriteLine("Last queued message received, setting scrollbar in 500ms");
                }
                SetScrollBar(forceScroll);
            }
        }
        private List<MessageModel> GetVisibleItems(LongListSelector lls)
        {
            var offset = this._viewPortControl.Viewport.Height;
            List<MessageModel> visibleItems = new List<MessageModel>();
            double totalHeight = 0;
            this.items = this.items.OrderBy(x => Canvas.GetTop(x.Value)).ToDictionary(x => x.Key, x => x.Value);
            foreach (KeyValuePair<MessageModel, ContentPresenter> kvp in this.items)
            {
                if ((totalHeight + kvp.Value.ActualHeight) < offset)
                {
                    visibleItems.Add((MessageModel)kvp.Key);
                    totalHeight += kvp.Value.ActualHeight;
                }
                else
                    break;
            }
            return visibleItems;
            //return items.Where(x => Canvas.GetTop(x.Value) + x.Value.ActualHeight > offset)
            //    .OrderBy(x => Canvas.GetTop(x.Value)).ToDictionary(x=>x.Key,x=>x.Value).Keys.ToList();
        }
        private static ViewportControl FindViewport(DependencyObject parent)
        {
            var childCount = VisualTreeHelper.GetChildrenCount(parent);
            for (var i = 0; i < childCount; i++)
            {
                var elt = VisualTreeHelper.GetChild(parent, i);
                if (elt is ViewportControl) return (ViewportControl)elt;
                var result = FindViewport(elt);
                if (result != null) return result;
            }
            return null;
        }

        private static ScrollBar FindScrollbar(DependencyObject parent)
        {
            var childCount = VisualTreeHelper.GetChildrenCount(parent);
            for (var i = 0; i < childCount; i++)
            {
                var elt = VisualTreeHelper.GetChild(parent, i);
                if (elt is ScrollBar) return (ScrollBar)elt;
                var result = FindScrollbar(elt);
                if (result != null) return result;
            }
            return null;
        }
        private static Thumb FindThumb(DependencyObject parent)
        {
            var childCount = VisualTreeHelper.GetChildrenCount(parent);
            for (var i = 0; i < childCount; i++)
            {
                var elt = VisualTreeHelper.GetChild(parent, i);
                if (elt is Thumb) return (Thumb)elt;
                var result = FindThumb(elt);
                if (result != null) return result;
            }
            return null;
        }
        private bool CheckIfScrolledToEnd(LongListSelector lls, out bool isScrollBarVisible)
        {
            isScrollBarVisible = false;
            ViewportControl viewPortCont = _viewPortControl;//FindViewport(lls);
            if (viewPortCont == null)
                return true;

            isScrollBarVisible = viewPortCont.Bounds.Height < 8388352.0 && (viewPortCont.Bounds.Height > viewPortCont.Viewport.Height);
            return viewPortCont.Viewport.Bottom == viewPortCont.Bounds.Bottom;
        }

        private void InputTextBox_KeyDown(object sender, System.Windows.Input.KeyEventArgs e)
        {
            TextBox textBox = sender as TextBox;
            if (e.Key == System.Windows.Input.Key.Enter && !String.IsNullOrEmpty(textBox.Text))
            {
                if (OnSendMessage != null)
                {
                    OnSendMessage(this, new SendMessageEventArgs(textBox.Text));
                }
                textBox.Text = string.Empty;
            }
        }

        public void SetScrollBar(bool forceScrollToEnd = false)
        {
            if (Messages==null || Messages.Count == 0 || DesignerProperties.IsInDesignTool)
                return;

            bool isScrollbarVisible;
            bool isScrolledToEnd = CheckIfScrolledToEnd(this.ChatBalloonList, out isScrollbarVisible);

            if (isScrolledToEnd || forceScrollToEnd)
            {
                this.ChatBalloonList.UpdateLayout();
                this.ChatBalloonList.ScrollTo(this.Messages.Last());
                if (this._viewPortControl != null)
                    this._viewPortControl.SetViewportOrigin(new Point(0, this._viewPortControl.Bounds.Bottom));
                if (this._blinkScrollbarThread != null)
                {
                    this._isScrollbarBlinking = false;
                }
            }
            else if (isScrollbarVisible)
            {
                if (this._blinkScrollbarThread == null)
                {
                    ThreadStart t = delegate
                        {
                            this._isScrollbarBlinking = true;
                            bool blinked = false;
                            int lastBlinkMs = 200;
                            while (true)
                            {
                                if (!this._isScrollbarBlinking)
                                {
                                    this.Dispatcher.BeginInvoke(() =>
                                    {
                                        this.BlinkGrid.Background = new SolidColorBrush(Colors.Transparent);
                                    });
                                    this._blinkScrollbarThread = null;
                                    break;
                                }
                                Thread.Sleep(10);

                                if (lastBlinkMs >= 200)
                                {
                                    this.Dispatcher.BeginInvoke(() =>
                                    {
                                        this.BlinkGrid.Background = blinked ? new SolidColorBrush(Colors.White) : new SolidColorBrush(Colors.Transparent);
                                        blinked = !blinked;
                                    });
                                    lastBlinkMs = 0;
                                }
                                lastBlinkMs += 10;
                            }

                        };
                    this._blinkScrollbarThread = new Thread(t) { IsBackground = true, Name = "BlinkScrollbarThread" };
                    this._blinkScrollbarThread.Start();
                }
            }
        }

        private void InputTextBox_GotFocus(object sender, RoutedEventArgs e)
        {
            if (!IsInputTextBlockFocused)
                IsInputTextBlockFocused = true;
        }

        private void InputTextBox_LostFocus(object sender, RoutedEventArgs e)
        {
            if (IsInputTextBlockFocused)
                IsInputTextBlockFocused = false;
        }

        private void UserControl_Loaded(object sender, RoutedEventArgs e)
        {
            if (DesignerProperties.IsInDesignTool)
                return;
            WP8PullDetector detector = new WP8PullDetector();
            detector.Bind(this.ChatBalloonList);
            detector.Compression += detector_Compression;
            _viewPortControl = FindViewport(this.ChatBalloonList);
            items = new Dictionary<MessageModel, ContentPresenter>();
            var rootFrame = Application.Current.RootVisual as PhoneApplicationFrame;
            rootFrame.RenderTransform = new CompositeTransform() { TranslateY = +0 };

            //var scrollBar = FindScrollbar(this.ChatBalloonList);
            //var thumb = FindThumb(this.ChatBalloonList);
            //thumb.DragStarted += thumb_DragStarted;
            //thumb.DragDelta += thumb_DragDelta;
            //thumb.DragCompleted += thumb_DragCompleted;

        }

        void thumb_DragCompleted(object sender, DragCompletedEventArgs e)
        {
            _viewPortControl.ManipulationLockMode = ManipulationLockMode.Vertical;
            // Thumb myThumb = sender as Thumb;
            Debug.WriteLine("thumb drag completed");
            // myThumb.Background = new SolidColorBrush(Colors.Orange);
        }

        private double _accumulatedChange;
        private int _scrolledIndex;
        private int _lastVisibleIndex;
        private DateTime _lastDeltaUpdate;
        void thumb_DragDelta(object sender, DragDeltaEventArgs e)
        {
            
            Debug.WriteLine("thumb delta change: " + e.VerticalChange);
            // Debug.WriteLine(String.Format("setting viewport to:{0}", this._viewPortControl.Viewport.Y + (e.VerticalChange*15)));
            _accumulatedChange += e.VerticalChange;

            if (DateTime.UtcNow.Subtract(_lastDeltaUpdate).TotalMilliseconds>7)
            {
                _lastDeltaUpdate = DateTime.UtcNow;
                double multipliedValue = this._viewPortControl.Viewport.Y + (_accumulatedChange * 18);
                //int count = (int)_accumulatedChange / 5;
                //if (count == 0)
                //    count = 1;
                //Debug.WriteLine(String.Format("skipping numer of items:{0}",count));
                //var visibleItems = GetVisibleItems(this.ChatBalloonList);
                ////List<MessageModel> allModels = this.items.Keys.ToList();
                //int lastVisibleIndex = this.Messages.IndexOf(visibleItems.Last());
                //if (lastVisibleIndex != _lastVisibleIndex)
                //{
                //    _lastVisibleIndex = lastVisibleIndex;
                //    if (_lastVisibleIndex != _scrolledIndex)
                //        _scrolledIndex = _lastVisibleIndex;
                //}
                //if (_scrolledIndex + count < this.Messages.Count)
                //{
                //    MessageModel next = this.Messages[_scrolledIndex + count];
                //    Debug.WriteLine(String.Format("scrolling to index nr :{0}, message: {1}", _scrolledIndex + count, next.MessageText));
                //    this.ChatBalloonList.ScrollTo(next);
                //    _scrolledIndex= _scrolledIndex+count;
                //}
                _viewPortControl.SetViewportOrigin(new Point(0,multipliedValue ));
                _accumulatedChange = 0;
            }
            //else if (_accumulatedChange < -15)
            //{
            //    double multipliedValue = this._viewPortControl.Viewport.Y + (_accumulatedChange * 15);

            //    //   Debug.WriteLine(String.Format("setting viewport to:{0}", multipliedValue));
            //    var visibleItems = GetVisibleItems(this.ChatBalloonList);
            //    List<MessageModel> allModels = this.items.Keys.ToList();
            //    int firstVisibleIndex = visibleItems.IndexOf(visibleItems.First());
            //    if (firstVisibleIndex >= 1)
            //    {
            //        Debug.WriteLine(String.Format("scrolling to item nr :{0}", firstVisibleIndex + 1));
            //        MessageModel prev = allModels[firstVisibleIndex - 1];
            //        this.ChatBalloonList.ScrollTo(prev);
            //    }
            //    //_viewPortControl.SetViewportOrigin(new Point(0,multipliedValue ));
            //    _accumulatedChange = 0;
            //}
        }

        void thumb_DragStarted(object sender, DragStartedEventArgs e)
        {
            _viewPortControl.ManipulationLockMode = ManipulationLockMode.Horizontal;
            //Thumb myThumb = sender as Thumb;
            // ICollection<object> visibleItems = this.ChatBalloonList.GetItemsInView();
            Debug.WriteLine("thumb drag started: " + e.VerticalOffset);
            //   myThumb.Background = new SolidColorBrush(Colors.Blue);

        }

        void detector_Compression(object sender, CompressionEventArgs e)
        {
            if (e.Type == CompressionType.Bottom)
            {
                _isScrollbarBlinking = false;
            }
        }

        private void MenuItem_Click(object sender, RoutedEventArgs e)
        {
            MenuItem menuItem = sender as MenuItem;
            if (menuItem != null)
            {
                switch (menuItem.Name)
                {
                    case "MenuItemCopy":
                        MessageModel model = menuItem.DataContext as MessageModel;
                        Clipboard.SetText(model.MessageText);
                        break;

                }
            }
        }

        private void ChatBalloonList_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            lock (LockObject)
            {
                if (DateTime.Now.Subtract(LastScrollAdjustment).TotalMilliseconds < 170)
                    return;
                if (e.NewSize.Height < e.PreviousSize.Height)
                {
                    LastScrollAdjustment = DateTime.Now;
                    Thread t = new Thread(() =>
                        {
                            Thread.Sleep(170);
                            this.Dispatcher.BeginInvoke(() =>
                            {
                                SetScrollBar(true);
                            });
                        });
                    t.Start();
                }
            }
        }

        private void ChatBalloonList_Tap(object sender, System.Windows.Input.GestureEventArgs e)
        {
            if (OnChatWindowTapped != null)
                OnChatWindowTapped(this, e);
        }

        private void ChatBalloonList_ItemRealized(object sender, ItemRealizationEventArgs e)
        {
            //if (e.ItemKind == LongListSelectorItemKind.Item)
            //{
            //    MessageModel o = (MessageModel)e.Container.DataContext;
            //    items[o] = e.Container;
            //}
        }

        private void ChatBalloonList_ItemUnrealized(object sender, ItemRealizationEventArgs e)
        {
            //if (e.ItemKind == LongListSelectorItemKind.Item)
            //{
            //    MessageModel o = (MessageModel)e.Container.DataContext;
            //    items.Remove(o);
            //}
        }
    }
    public class SendMessageEventArgs : EventArgs
    {
        public string Text { get; protected set; }

        public SendMessageEventArgs(string text)
        {
            this.Text = text;
        }
    }
}
